%top {
/* Include this before everything else, for various large-file definitions */
#include "config.h"
#include <wireshark.h>
}

/*
 * We want a reentrant scanner.
 */
%option reentrant

/*
 * We don't use input, so don't generate code for it.
 */
%option noinput

/*
 * We don't use unput, so don't generate code for it.
 */
%option nounput

/*
 * We don't read interactively from the terminal.
 */
%option never-interactive

/*
 * We want to stop processing when we get to the end of the input.
 */
%option noyywrap

/*
 * The type for the state we keep for a scanner.
 */
%option extra-type="k12text_state_t *"

/*
 * Prefix scanner routines with "k12text_" rather than "yy", so this scanner
 * can coexist with other scanners.
 */
%option prefix="k12text_"

%option outfile="k12text.c"

/* Options useful for debugging				*/
/* noline:  Prevent generation of #line directives	*/
/*	    Seems to be required when using the		*/
/*	    Windows VS debugger so as to be able	*/
/*	    to properly step through the code and	*/
/*	    set breakpoints & etc using the		*/
/*	    k12text.c file rather than the		*/
/*	    k12text.l file				*/
/*	XXX: %option noline gives an error message:	*/
/*	    "unrecognized %option: line"		*/
/*	    with flex 2.5.35; the --noline		*/
/*	    command-line option works OK.		*/
/*							*/
/* debug:   Do output of "rule acceptance" info		*/
/*	    during parse				*/
/*							*/
/* %option noline  */
/* %option debug   */

/*
 * We have to override the memory allocators so that we don't get
 * "unused argument" warnings from the yyscanner argument (which
 * we don't use, as we have a global memory allocator).
 *
 * We provide, as macros, our own versions of the routines generated by Flex,
 * which just call malloc()/realloc()/free() (as the Flex versions do),
 * discarding the extra argument.
 */
%option noyyalloc
%option noyyrealloc
%option noyyfree

%{
/* k12text.l
 *
 * Wiretap Library
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

 /*
  * TODO:
  *   - fix timestamps after midnight
  *   - verify encapsulations
  */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "wtap-int.h"
#include "wtap.h"
#include "file_wrappers.h"
#include <wsutil/buffer.h>
#include "k12.h"

#ifndef HAVE_UNISTD_H
#define YY_NO_UNISTD_H
#endif

/*
 * Disable diagnostics in the code generated by Flex.
 */
DIAG_OFF_FLEX()

/*
 * State kept by the scanner.
 */
typedef struct {
	FILE_T fh;
	int err;
	char *err_info;
	int start_state;

	unsigned g_h;
	unsigned g_m;
	unsigned g_s;
	unsigned g_ms;
	unsigned g_ns;
	int g_encap;
	uint8_t *bb;
	unsigned ii;
	bool is_k12text;
	bool at_eof;
	unsigned junk_chars;
	char* error_str;
	uint64_t file_bytes_read;
	bool ok_frame;
} k12text_state_t;

#define KERROR(text) do { yyextra->error_str = g_strdup(text); yyterminate(); } while(0)
#define SET_HOURS(text) yyextra->g_h = (unsigned) strtoul(text,NULL,10)
#define SET_MINUTES(text) yyextra->g_m = (unsigned) strtoul(text,NULL,10)
#define SET_SECONDS(text) yyextra->g_s = (unsigned) strtoul(text,NULL,10)
#define SET_MS(text) yyextra->g_ms = (unsigned) strtoul(text,NULL,10)
#define SET_NS(text) yyextra->g_ns = (unsigned) strtoul(text,NULL,10)
#define ADD_BYTE(text) do {if (yyextra->ii >= WTAP_MAX_PACKET_SIZE_STANDARD) {KERROR("frame too large");} yyextra->bb[yyextra->ii++] = (uint8_t)strtoul(text,NULL,16); } while(0)
#define FINALIZE_FRAME() do { yyextra->ok_frame = true; } while (0)
/*~ #define ECHO*/
#define YY_USER_ACTION yyextra->file_bytes_read += yyleng;
#define YY_USER_INIT { \
	k12text_state_t *scanner_state = k12text_get_extra(yyscanner); \
	BEGIN(scanner_state->start_state); \
}
#define YY_INPUT(buf,result,max_size) { \
	k12text_state_t *scanner_state = k12text_get_extra(yyscanner); \
	int c = file_getc(scanner_state->fh); \
	if (c == EOF) { \
		scanner_state->err = file_error(scanner_state->fh, \
		    &scanner_state->err_info); \
		if (scanner_state->err == 0) \
			scanner_state->err = WTAP_ERR_SHORT_READ; \
		result = YY_NULL; \
	} else { \
		buf[0] = c; \
		result = 1; \
	} \
}
#define MAX_JUNK 400000
#define ECHO

/*
 * Private per-file data.
 */
typedef struct {
	/*
	 * The file position after the end of the previous frame processed by
	 * k12text_read.
	 *
	 * We need to keep this around, and seek to it at the beginning of
	 * each call to k12text_read(), since the lexer undoubtedly did some
	 * amount of look-ahead when processing the previous frame.
	 */
	int64_t	next_frame_offset;
} k12text_t;

/*
 * Sleazy hack to suppress compiler warnings in yy_fatal_error().
 */
#define YY_EXIT_FAILURE ((void)yyscanner, 2)

/*
 * Macros for the allocators, to discard the extra argument.
 */
#define k12text_alloc(size, yyscanner)		(void *)malloc(size)
#define k12text_realloc(ptr, size, yyscanner)	(void *)realloc((char *)(ptr), (size))
#define k12text_free(ptr, yyscanner)		free((char *)ptr)

static int k12text_file_type_subtype = -1;

void register_k12text(void);

%}
start_timestamp \053[\055]{9}\053[\055]{15,100}\053[\055]{10,100}\053
oneormoredigits [0-9]+:
twodigits [0-9][0-9]
colon :
comma ,
threedigits [0-9][0-9][0-9]
start_bytes \174\060\040\040\040\174
bytes_junk \174[A-F0-9][A-F0-9\040][A-F0-9\040][A-F0-9\040]\174
byte [a-f0-9][a-f0-9]\174
end_bytes \015?\012\015?\012
eth ETHER
mtp2 MTP-L2
sscop SSCOP
sscfnni SSCF
hdlc HDLC

%START MAGIC NEXT_FRAME HOURS MINUTES M2S SECONDS S2M MS M2N NS ENCAP STARTBYTES BYTE
%%
<MAGIC>{start_timestamp}  { yyextra->is_k12text = true; yyterminate(); }

<MAGIC>. { if (++ yyextra->junk_chars > MAX_JUNK) { yyextra->is_k12text = false;  yyterminate(); } }

<NEXT_FRAME>{start_timestamp} {BEGIN(HOURS); }
<HOURS>{oneormoredigits} { SET_HOURS(yytext); BEGIN(MINUTES); }
<MINUTES>{twodigits} { SET_MINUTES(yytext); BEGIN(M2S);}
<M2S>{colon} { BEGIN(SECONDS);}
<SECONDS>{twodigits} { SET_SECONDS(yytext); BEGIN(S2M); }
<S2M>{comma}  { BEGIN(MS); }
<MS>{threedigits} { SET_MS(yytext); BEGIN(M2N);  }
<M2N>{comma}  { BEGIN(NS); }
<NS>{threedigits} { SET_NS(yytext); BEGIN(ENCAP);}
<ENCAP>{eth} {yyextra->g_encap = WTAP_ENCAP_ETHERNET; BEGIN(STARTBYTES); }
<ENCAP>{mtp2} {yyextra->g_encap = WTAP_ENCAP_MTP2; BEGIN(STARTBYTES); }
<ENCAP>{sscop} {yyextra->g_encap = WTAP_ENCAP_ATM_PDUS; BEGIN(STARTBYTES); }
<ENCAP>{sscfnni} {yyextra->g_encap = WTAP_ENCAP_MTP3; BEGIN(STARTBYTES); }
<ENCAP>{hdlc} {yyextra->g_encap = WTAP_ENCAP_CHDLC; BEGIN(STARTBYTES); }
<ENCAP,STARTBYTES>{start_bytes} { BEGIN(BYTE); }
<BYTE>{byte} { ADD_BYTE(yytext); }
<BYTE>{bytes_junk} ;
<BYTE>{end_bytes} { FINALIZE_FRAME(); yyterminate(); }

. {  if (++yyextra->junk_chars > MAX_JUNK) { KERROR("too much junk");  } }
<<EOF>> { yyextra->at_eof = true; yyterminate(); }

%%

/*
 * Turn diagnostics back on, so we check the code that we've written.
 */
DIAG_ON_FLEX()

/* Fill in pkthdr */

static bool
k12text_set_headers(wtap_rec *rec, k12text_state_t *state,
    int *err, char **err_info)
{
	rec->rec_type = REC_TYPE_PACKET;
	rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
	rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;

	rec->ts.secs = 946681200 + (3600*state->g_h) + (60*state->g_m) + state->g_s;
	rec->ts.nsecs = 1000000*state->g_ms + 1000*state->g_ns;

	rec->rec_header.packet_header.caplen = rec->rec_header.packet_header.len = state->ii;

	rec->rec_header.packet_header.pkt_encap = state->g_encap;

	/* The file-encap is WTAP_ENCAP_PER_PACKET */
	switch(state->g_encap) {
	    case WTAP_ENCAP_ETHERNET:
		    rec->rec_header.packet_header.pseudo_header.eth.fcs_len = 0;
		    break;
	    case WTAP_ENCAP_MTP3:
	    case WTAP_ENCAP_CHDLC:
		    /* no pseudo_header to fill in for these types */
		    break;
	    case WTAP_ENCAP_MTP2:      /* not (yet) supported		*/
		    /* XXX: I don't know how to fill in the		*/
		    /* pseudo_header for these types.			*/
		    *err = WTAP_ERR_UNSUPPORTED;
		    *err_info = g_strdup("k12text: MTP2 packets not yet supported");
		    return false;
	    case WTAP_ENCAP_ATM_PDUS:  /* not (yet) supported		*/
		    /* XXX: I don't know how to fill in the		*/
		    /* pseudo_header for these types.			*/
		    *err = WTAP_ERR_UNSUPPORTED;
		    *err_info = g_strdup("k12text: SSCOP packets not yet supported");
		    return false;
	    default:
		    *err = WTAP_ERR_UNSUPPORTED;
		    *err_info = g_strdup("k12text: unknown encapsulation type");
		    return false;
	}
	return true;
}

/* Note: k12text_reset is called each time data is to be processed from	*/
/*       a file. This ensures that no "state" from a previous read is	*/
/*       used (such as the lexer look-ahead buffer, file_handle, file	*/
/*       position and so on. This allows a single lexer buffer to be	*/
/*       used even when multiple files are open simultaneously (as for	*/
/*       a file merge).							*/

static bool
k12text_run_scanner(k12text_state_t *state, FILE_T fh, int start_state,
    int *err, char **err_info)
{
	yyscan_t scanner = NULL;

	if (yylex_init(&scanner) != 0) {
		/* errno is set if this fails */
		*err = errno;
		*err_info = NULL;
		return false;
	}
	state->fh = fh;
	state->err = 0;
	state->err_info = NULL;
	state->start_state = start_state;

	state->g_encap = WTAP_ENCAP_UNKNOWN;
	state->ok_frame = false;
	state->is_k12text = false;
	state->at_eof = false;
	state->junk_chars = 0;
	state->error_str = NULL;
	state->file_bytes_read=0;
	state->g_h=0;
	state->g_m=0;
	state->g_s=0;
	state->g_ns=0;
	state->g_ms=0;
	state->ii=0;

	/* Associate the state with the scanner */
	k12text_set_extra(state, scanner);

	yylex(scanner);
	yylex_destroy(scanner);
	if (state->err != 0 && state->err != WTAP_ERR_SHORT_READ) {
		/* I/O error. */
		*err = state->err;
		*err_info = state->err_info;
		return false;
	}
	return true;
}

static bool
k12text_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, char ** err_info, int64_t *data_offset)
{
	k12text_t *k12text = (k12text_t *)wth->priv;
	k12text_state_t state;

	/*
	 * We seek to the file position after the end of the previous frame
	 * processed by k12text_read(), since the lexer undoubtedly did some
	 * amount of look-ahead when processing the previous frame.
	 *
	 * We also clear out any lexer state (eg: look-ahead buffer) and
	 * init vars set by lexer.
	 */

	if ( file_seek(wth->fh, k12text->next_frame_offset, SEEK_SET, err) == -1) {
		return false;
	}
	state.bb = (uint8_t*)g_malloc(WTAP_MAX_PACKET_SIZE_STANDARD);

	if (!k12text_run_scanner(&state, wth->fh, NEXT_FRAME, err, err_info)) {
		g_free(state.bb);
		return false;
	}

	if (state.ok_frame == false) {
		if (state.at_eof) {
			*err = 0;
			*err_info = NULL;
		} else {
			*err = WTAP_ERR_BAD_FILE;
			*err_info = state.error_str;
		}
		g_free(state.bb);
		return false;
	}

	*data_offset = k12text->next_frame_offset;           /* file position for beginning of this frame   */
	k12text->next_frame_offset += state.file_bytes_read; /* file position after end of this frame       */

	if (!k12text_set_headers(rec, &state, err, err_info)) {
		g_free(state.bb);
		return false;
	}
	ws_buffer_assure_space(buf, rec->rec_header.packet_header.caplen);
	memcpy(ws_buffer_start_ptr(buf), state.bb, rec->rec_header.packet_header.caplen);

	g_free(state.bb);
	return true;
}

static bool
k12text_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, Buffer *buf, int *err, char **err_info)
{
	k12text_state_t state;

	if ( file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
		return false;
	}
	state.bb = (uint8_t*)g_malloc(WTAP_MAX_PACKET_SIZE_STANDARD);

	if (!k12text_run_scanner(&state, wth->random_fh, NEXT_FRAME, err, err_info)) {
		g_free(state.bb);
		return false;
	}

	if (state.ok_frame == false) {
		*err = WTAP_ERR_BAD_FILE;
		if (state.at_eof) {
			/* What happened ? The desired frame was previously read without a problem */
			*err_info = g_strdup("Unexpected EOF (program error ?)");
		} else {
			*err_info = state.error_str;
		}
		g_free(state.bb);
		return false;
	}

	if (!k12text_set_headers(rec, &state, err, err_info)) {
		g_free(state.bb);
		return false;
	}
	ws_buffer_assure_space(buf, rec->rec_header.packet_header.caplen);
	memcpy(ws_buffer_start_ptr(buf), state.bb, rec->rec_header.packet_header.caplen);

	g_free(state.bb);
	return true;
}

wtap_open_return_val
k12text_open(wtap *wth, int *err, char **err_info)
{
	k12text_t *k12text;
	k12text_state_t state;

	state.bb = (uint8_t*)g_malloc(WTAP_MAX_PACKET_SIZE_STANDARD);
	if (!k12text_run_scanner(&state, wth->fh, MAGIC, err, err_info)) {
		g_free(state.bb);
		return WTAP_OPEN_ERROR;
	}

	if (!state.is_k12text) {
		/* *err might have been set to WTAP_ERR_SHORT_READ */
		*err = 0;
		g_free(state.bb);
		return WTAP_OPEN_NOT_MINE;
	}

	if ( file_seek(wth->fh, 0, SEEK_SET, err) == -1) {
		g_free(state.bb);
		return WTAP_OPEN_ERROR;
	}

	k12text = g_new(k12text_t, 1);
	wth->priv = (void *)k12text;
	k12text->next_frame_offset = 0;
	wth->file_type_subtype = k12text_file_type_subtype;
	wth->file_encap = WTAP_ENCAP_PER_PACKET;
	wth->snapshot_length = 0;
	wth->subtype_read = k12text_read;
	wth->subtype_seek_read = k12text_seek_read;
	wth->file_tsprec = WTAP_TSPREC_NSEC;

	g_free(state.bb);
	return WTAP_OPEN_MINE;
}


static const struct { int e; const char* s; } encaps[] = {
	{ WTAP_ENCAP_ETHERNET, "ETHER" },
	{ WTAP_ENCAP_MTP2, "MTP-L2" },
	{ WTAP_ENCAP_ATM_PDUS, "SSCOP" },
	{ WTAP_ENCAP_MTP3, "SSCF" },
	{ WTAP_ENCAP_CHDLC, "HDLC" },
	/* ... */
	{ 0, NULL }
};

static bool
k12text_dump(wtap_dumper *wdh, const wtap_rec *rec,
	     const uint8_t *pd, int *err, char **err_info _U_) {
#define K12BUF_SIZE 196808
	char *buf;
	size_t left = K12BUF_SIZE;
	size_t wl;
	char *p;
	const char* str_enc;
	unsigned i;
	unsigned ns;
	unsigned ms;
	bool ret;
	struct tm *tmp;

	/* Don't write anything bigger than we're willing to read. */
	if (rec->rec_header.packet_header.caplen > WTAP_MAX_PACKET_SIZE_STANDARD) {
		*err = WTAP_ERR_PACKET_TOO_LARGE;
		return false;
	}

	str_enc = NULL;
	for(i=0; encaps[i].s; i++) {
		if (rec->rec_header.packet_header.pkt_encap == encaps[i].e) {
			str_enc = encaps[i].s;
			break;
		}
	}
	if (str_enc == NULL) {
		/*
		 * That encapsulation type is not supported.  Fail.
		 */
		*err = WTAP_ERR_UNWRITABLE_ENCAP;
		return false;
	}

	buf = (char *)g_malloc(K12BUF_SIZE);
	p = buf;

	ms = rec->ts.nsecs / 1000000;
	ns = (rec->ts.nsecs - (1000000*ms))/1000;

	tmp = gmtime(&rec->ts.secs);
	if (tmp == NULL)
		snprintf(p, 90, "+---------+---------------+----------+\r\nXX:XX:XX,");
	else
		strftime(p, 90, "+---------+---------------+----------+\r\n%H:%M:%S,", tmp);
	wl = strlen(p);
	p += wl;
	left -= wl;

	wl = snprintf(p, left, "%.3d,%.3d   %s\r\n|0   |", ms, ns, str_enc);
	p += wl;
	left -= wl;

	for(i = 0; i < rec->rec_header.packet_header.caplen && left > 2; i++) {
		wl = snprintf(p, left, "%.2x|", pd[i]);
		p += wl;
		left -= wl;
	}

	wl = snprintf(p, left, "\r\n\r\n");
	left -= wl;

	ret = wtap_dump_file_write(wdh, buf, K12BUF_SIZE - left, err);

	g_free(buf);
	return ret;
}


static bool
k12text_dump_open(wtap_dumper *wdh, int *err _U_, char **err_info _U_)
{
    wdh->subtype_write = k12text_dump;

    return true;
}

static int
k12text_dump_can_write_encap(int encap)
{
    switch (encap) {
	case WTAP_ENCAP_PER_PACKET:
	case WTAP_ENCAP_ETHERNET:
	case WTAP_ENCAP_MTP3:
	case WTAP_ENCAP_CHDLC:
		return 0;
	case WTAP_ENCAP_MTP2:
	case WTAP_ENCAP_ATM_PDUS:
	default:
		return WTAP_ERR_UNWRITABLE_ENCAP;
    }
}

static const struct supported_block_type k12text_blocks_supported[] = {
    /*
     * We support packet blocks, with no comments or other options.
     */
    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
};

static const struct file_type_subtype_info k12text_info = {
    "K12 text file", "k12text", "txt", NULL,
    false, BLOCKS_SUPPORTED(k12text_blocks_supported),
    k12text_dump_can_write_encap, k12text_dump_open, NULL
};

void register_k12text(void)
{
    k12text_file_type_subtype = wtap_register_file_type_subtype(&k12text_info);

    /*
     * Register name for backwards compatibility with the
     * wtap_filetypes table in Lua.
     */
    wtap_register_backwards_compatibility_lua_name("K12TEXT",
                                                   k12text_file_type_subtype);
}
